package com.rudolfheszele.smsselector.controller;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;

import android.app.Activity;
import android.content.Intent;

import com.rudolfheszele.smsselector.R;
import com.rudolfheszele.smsselector.model.SmsSelectorRuleSet;
import com.rudolfheszele.smsselector.model.SmsSelectorRuleSetEvaluationEngine;
import com.rudolfheszele.smsselector.model.SmsSelectorSmsMessage;
import com.rudolfheszele.smsselector.view.View;

/**
 * This class is the main part of the application, holds all the logic needed for the functionality
 * @author Rudolf Heszele
 * @version 0.2
 */
public class SmsSelectorMainController implements Controller
{
	/**
	 * This static method must to be called before using the controller.
	 */
	public static void initialize()
	{
		ControllerFactoryFactory.setControllerFactory(new SmsSelectorMainControllerFactory());
	}
	
	/**
	 * The default constructor. Initializes member variables.
	 */
	public SmsSelectorMainController()
	{
		mControllers = new TreeMap<Integer, Controller>();
		mRuleSets = null;
		mSmsMessages = null;
		mCachedMessages = null;
		mSmsReaderServiceIntent = null;
		mSmsReaderController = null;
	}
	
	/**
	 * This method called from the View, when it wishes to register itself to the controller, to control it.
	 * @param view The view to register
	 * @see Controller
	 */
	@Override
	public void registerView(View view)
	{
		Activity activity = (Activity)view;	
		switch(view.getId())
		{
			case R.layout.smsselector_main_activity_layout:
				if(mControllers.get(view.getId()) != null)
					mControllers.get(view.getId()).registerView(view);
				else
					mControllers.put(view.getId(), new SmsSelectorMainActivityController(activity));
				break;
			case R.layout.smsselector_sms_viewer_activity_layout:
				if(mControllers.get(view.getId()) != null)
					mControllers.get(view.getId()).registerView(view);
				else
					mControllers.put(view.getId(), new SmsSelectorSmsViewerActivityController(activity));
				break;
			case R.layout.smsselector_message_viewer_layout:
				if(mControllers.get(view.getId()) != null)
					mControllers.get(view.getId()).registerView(view);
				else
					mControllers.put(view.getId(), new SmsSelectorMessageViewerActivityController(activity));
				break;
			case R.layout.smsselector_rules_viewer_activity_layout:
				if(mControllers.get(view.getId()) != null)
					mControllers.get(view.getId()).registerView(view);
				else
					mControllers.put(view.getId(), new SmsSelectorRulesViewerActivityController(activity));
				break;
			case R.layout.smsselector_rules_creator_activity_layout:
				if(mControllers.get(view.getId()) != null)
					mControllers.get(view.getId()).registerView(view);
				else
					mControllers.put(view.getId(), new SmsSelectorRuleCreatorActivityController(activity));
				break;
		}
	}
	
	/**
	 * This method is called from the View, when an update is needed
	 * @param view The view, which needs the update
	 * @see Controller
	 */
	@Override
	public void updateView(View view)
	{
		mControllers.get(view.getId()).updateView(view);
	}
	
	/**
	 * Returns the View associated with the given id
	 * @param The id of the View
	 * @return The View which has the id
	 * @see Controller
	 */
	@Override
	public View getView(int viewId)
	{
		return mControllers.get(viewId).getView(0);
	}
	
	/**
	 * Executes the given task on the UI thread - mainly for UI update
	 * @param viewId The Activity's Id, the task needs to be executed on
	 * @param task The task which needs to be executed
	 * @see Controller
	 */
	@Override
	public void updateGui(int viewId, Runnable task)
	{
		Activity activity = getActivity(viewId);
		
		activity.runOnUiThread(task);
	}
	
	/**
	 * This method is called, when a menu is needed by the View
	 * @see Controller
	 */
	@Override
	public boolean buildMenu(View view, Object menu, Object clicked)
	{
		Controller controller = mControllers.get(view.getId());
		if(controller != null)
			return controller.buildMenu(view, menu, clicked);
		
		return false;
	}
	
	/**
	 * This method is called, when a menu is chosen
	 * @see Controller
	 */
	@Override
	public boolean menuClicked(View view, Object menu)
	{
		Controller controller = mControllers.get(view.getId());
		if(controller != null)
			return controller.menuClicked(view, menu);
		
		return false;
	}
	
	/**
	 * @see Controller
	 */
	@Override
	public void saveState(int viewId, Object state)
	{
		Controller controller = mControllers.get(viewId);
		
		if(controller != null)
			controller.saveState(viewId, state);
	}
	
	/**
	 * @see Controller
	 */
	@Override
	public void restoreState(int viewId, Object state)
	{
		Controller controller = mControllers.get(viewId);
		
		if(controller != null)
			controller.restoreState(viewId, state);
	}
	
	/**
	 * This method can be called, by other Controllers, to start the service to read the messages
	 * @param controller The controller, which wants to read the messages
	 */
	void startSmsReaderService(SmsReaderController controller)
	{
		Activity activity = (Activity)controller.getView(0);
		
		mSmsReaderController = controller;
		mSmsMessages = null;
		mSmsReaderServiceIntent = new Intent(activity, SmsSelectorSmsReaderService.class);
		activity.startService(mSmsReaderServiceIntent);
	}
	
	/**
	 * This method is used by the SMS reader service to register itself when it starts to reading the messages at startup
	 * @param smsReaderService The started service
	 */
	void registerSmsReaderService(SmsSelectorSmsReaderService smsReaderService)
	{
		mSmsReaderController.smsReadingStarted(smsReaderService);
		smsReaderService.addController(mSmsReaderController);
	}
	
	/**
	 * This method is called by the reader controller, or the reader thread itself, to stop the reading
	 */
	void stopSmsReaderService()
	{
		//Activity activity = (Activity)mSmsReaderController.getView(0);
		
		// Do not stop the service, we need to keep the process alive...
		// activity.stopService(mSmsReaderServiceIntent);
		mSmsReaderServiceIntent = null;
		mSmsReaderController = null;
		// Check if there were any incoming messages during the messages reading
		if(mCachedMessages != null)
			addSmsMessages((SmsSelectorSmsMessage[])mCachedMessages.toArray());
		mCachedMessages = null;
	}
	
	/**
	 * This method checks if the messages reading is in progress or not
	 * @return True is any controller is reading the messages, false otherwise
	 */
	boolean isSmsReadingInProgress()
	{
		return mSmsReaderServiceIntent != null;
	}
	
	/**
	 * This method cashes any incoming messages during messages reading phase
	 * @param smsMessage The message to cache
	 */
	void cacheIncomingMessage(SmsSelectorSmsMessage smsMessage)
	{
		if(mCachedMessages == null)
			mCachedMessages = new LinkedList<SmsSelectorSmsMessage>();
		mCachedMessages.add(smsMessage);
	}
	
	/**
	 * This method is the getter of the rule sets stored in the SQLite database
	 * @return A linked list, filled with the RuleSets
	 */
	LinkedList<SmsSelectorRuleSet> getRuleSets()
	{
		return mRuleSets;
	}
	
	/**
	 * This method is the setter of the rule sets stored in the SQLite database
	 * @param ruleSets A LinkedList storing every SmsSelectorRuleSet object
	 */
	void setRuleSets(LinkedList<SmsSelectorRuleSet> ruleSets)
	{
		mRuleSets = ruleSets;
	}

	/**
	 * This method stores the RuleSet into the database
	 * @param ruleSet The RuleSet to be stored
	 * @see SmsSelectorRuleSet
	 */
	void saveRuleSet(SmsSelectorRuleSet ruleSet)
	{
		if(mRuleSets.contains(ruleSet) == false)
		{
			mRuleSets.add(ruleSet);
			SmsSelectorRuleSet.insertRuleSetIntoDatabase(ruleSet);
		}
		else
		{
			// This is only an update, so we only need to update the database...
			SmsSelectorRuleSet.updateRuleSetInDatabase(ruleSet);
		}
	}
	
	/**
	 * This method removes the RuleSet from the database
	 * @param ruleSet The RuleSet to be deleted
	 * @see SmsSelectorRuleSet
	 */
	void removeRuleSet(SmsSelectorRuleSet ruleSet)
	{
		if(mRuleSets.contains(ruleSet) == true)
		{
			mRuleSets.remove(ruleSet);
			SmsSelectorRuleSet.deleteRuleSetFromDatabase(ruleSet);
		}
	}
	
	/**
	 * Finds the RuleSet with the given name
	 * @param name The name of the RuleSet
	 * @return The RuleSet object or null
	 * @see SmsSelectorRuleSet
	 */
	SmsSelectorRuleSet getRuleSetByName(String name)
	{
		SmsSelectorRuleSet returnRuleSet = null;
		Iterator<SmsSelectorRuleSet> ruleSetIterator = mRuleSets.iterator();
		
		while(ruleSetIterator.hasNext())
		{
			SmsSelectorRuleSet ruleSet = ruleSetIterator.next();
			
			if(ruleSet.getName().equals(name))
			{
				returnRuleSet = ruleSet;
				break;
			}
		}
		
		return returnRuleSet;
	}
	
	/**
	 * This method is the getter of the SMS messages from the device, after the rule sets have been applied on them
	 * @return The vector containing the messages
	 */
	TreeMap<String, LinkedList<SmsSelectorSmsMessage>> getSmsMessages()
	{
		return mSmsMessages;
	}
	
	/**
	 * The setter method of the messages
	 * @param smsMessages The vector containing the messages from the device, after the rule sets have been applied
	 */
	void addSmsMessages(SmsSelectorSmsMessage[] smsMessages)
	{	
		if(mSmsMessages == null)
			mSmsMessages = new TreeMap<String, LinkedList<SmsSelectorSmsMessage>>();
		for(int i = 0; i < smsMessages.length; i++)
		{
			LinkedList<SmsSelectorSmsMessage> messages = null;
			
			messages = mSmsMessages.get(smsMessages[i].getLogicalFolder());
			if(messages == null)
			{
				messages = new LinkedList<SmsSelectorSmsMessage>();
				mSmsMessages.put(smsMessages[i].getLogicalFolder(), messages);
			}
			messages.add(smsMessages[i]);
		}
	}
	
	/**
	 * Deletes every messages
	 */
	void eraseSmsMessages()
	{
		if(mSmsMessages == null)
			mSmsMessages.clear();
	}
	
	/**
	 * Finds the message with the given ID, in the given folder
	 * @param messageId The ID of the message
	 * @param folder The folder the message should be in
	 * @return The message itself or null
	 * @see SmsSelectorSmsMessage
	 */
	SmsSelectorSmsMessage findSmsMessage(int messageId, String folder)
	{
		SmsSelectorSmsMessage returnMessage = null;
		LinkedList<SmsSelectorSmsMessage> messages = mSmsMessages.get(folder);
		Iterator<SmsSelectorSmsMessage> messagesIterator = messages.iterator();
		
		while(messagesIterator.hasNext())
		{
			SmsSelectorSmsMessage message = messagesIterator.next();
			
			if(message.getMessageId() == messageId)
			{
				returnMessage = message;
				break;
			}
		}
		
		return returnMessage;
	}
	
	/**
	 * Adds a newly received message to the internal storage, after applying the rules on it.
	 * @param message The newly received message
	 * @return true if a rule matched on the message, false otherwise
	 */
	boolean newSmsMessage(SmsSelectorSmsMessage message)
	{
		Iterator<SmsSelectorRuleSet> ruleSetIterator = mRuleSets.iterator();
		boolean returnValue = false;
		SmsSelectorSmsMessage[] messageArray = new SmsSelectorSmsMessage[1];
		
		messageArray[0] = message;
		while(ruleSetIterator.hasNext())
		{
			SmsSelectorRuleSet ruleSet = ruleSetIterator.next();
    		if(SmsSelectorRuleSetEvaluationEngine.isRuleSetMatch(ruleSet, message) == true)
    		{
    			ruleSet.getAction().doAction(message);
    			returnValue = true;
    			break;
    		}
		}
		addSmsMessages(messageArray);
		return returnValue;
	}
	
	/**
	 * This is a helper method for other controller classes to reach Activities, to update the GUI
	 * @param viewId The Activity's Id
	 * @return The Activity of the given Id or null
	 */
	Activity getActivity(int viewId)
	{
		return (Activity)mControllers.get(viewId).getView(0);
	}
	
	/**
	 * A callback used by SmsSelector SmsReaderService to indicate if the SMS reading is finished
	 */
	void smsReadingFinished()
	{
		SmsSelectorMainActivityController controller = (SmsSelectorMainActivityController)mControllers.get(R.layout.smsselector_main_activity_layout);
		
		controller.smsReadingFinished();
	}
	
	private TreeMap<Integer, Controller> mControllers;
	private LinkedList<SmsSelectorRuleSet> mRuleSets;
	private TreeMap<String, LinkedList<SmsSelectorSmsMessage>> mSmsMessages;
	private LinkedList<SmsSelectorSmsMessage> mCachedMessages;
	private Intent mSmsReaderServiceIntent;
	private SmsReaderController mSmsReaderController;
}
